package domain.message ;

import java.util.HashSet;
import java.util.Set;

import persistence.CorruptDataException;
import persistence.PersistenceFactory;
import persistence.repository.DirectoryRepository;
import persistence.repository.PostRepository;
import persistence.repository.ThreadRepository;
import util.Log;
import util.NotFoundException;
import util.Subsystem;

/**
 * the repository for directories, threads and posts. any request for a such objects goes through here.
 * objects are stored to and loaded from persistent media. a URI for that media is needed.
 *
 * the server guarantees that if it empty, then the first directory to be created has the id 0.
 *
 * @author idan
 */
public class MessageServer
{
	private final DirectoryRepository directories ;
	private final PostRepository posts ;
	private final ThreadRepository threads ;

	/**
	 * @param persistenceGenerator object to generate persistence controllers.
	 * @throws CorruptDataException if the data is invalid
	 */
	public MessageServer(final PersistenceFactory persistenceGenerator) throws CorruptDataException
	{
		Log.getLogger(Subsystem.REPOSITORY).info("starting message server") ;
		this.posts = new PostRepository(persistenceGenerator.newPostPersistence()) ;
		this.threads = new ThreadRepository(persistenceGenerator.newThreadPersistence()) ;
		this.directories = new DirectoryRepository(persistenceGenerator.newDirecoryPersistence()) ;
		Log.getLogger(Subsystem.REPOSITORY).info("message server started: " + this.toString()) ;
	}

	/**
	 * shut down this server.
	 * @post all information saved to persistence.
	 */
	public void close()
	{
		Log.getLogger(Subsystem.REPOSITORY).info("message server shutting down") ;
		this.posts.close() ;
		this.threads.close() ;
		this.directories.close() ;
		Log.getLogger(Subsystem.REPOSITORY).info("message server is down") ;
	}

	/**
	 * @param name name of the new directory.
	 * @return the newly created directory.
	 * @post return.getName() = name.
	 * @post directory exists in the repository.
	 */
	public Directory createDirectory(final String name)
	{
		return this.directories.create(name) ;
	}

	/**
	 * @param text text of the new post.
	 * @param authorId id of the author of the new post.
	 * @param threadParentId - the id of the parent thread
	 * @return the newly created post.
	 * @post return.getMessage() = text.
	 * @post return.getWriterId() = authorId.
	 * @post post exists in the repository.
	 */
	public Post createPost(final String text, final long authorId, final long threadParentId)
	{
		return this.posts.create(text, authorId, threadParentId) ;
	}

	/**
	 * @param topic topic for the new thread.
	 * @return the newly created thread.
	 * @post return.getTopic() = topic.
	 * @post thread exists in the repository.
	 */
	public Thread createThread(final String topic)
	{
		return this.threads.create(topic) ;
	}

	/**
	 * @param id id of the directory to delete.
	 * @throws NotFoundException if the directory does not exist.
	 * @post the directory whose id is 'id', if exists, is deleted and every subdirectory and thread belonging
	 * to it are deleted as well.
	 */
	public void deleteDirectory(final long id) throws NotFoundException
	{
		final Directory directory = this.getDirectory(id) ;
		for (final Long threadId: directory.getThreads())
		{
			this.deleteThread(threadId) ;
		}
		for (final Long directoryId: directory.getChildren())
		{
			this.deleteDirectory(directoryId) ;
		}
		this.directories.destroy(id) ;
	}

	/**
	 * @param id id of the post to delete.
	 * @throws NotFoundException if the post does not exist.
	 * @post the post whose id is 'id' if exists, is deleted.
	 */
	public void deletePost(final long id) throws NotFoundException
	{
		this.posts.destroy(id) ;
	}

	/**
	 * @param id id of the thread to delete.
	 * @throws NotFoundException if the thread does not exist.
	 * @post the thread whose id is 'id' if exists, is deleted, and every post belonging to it is deleted as
	 * well.
	 */
	public void deleteThread(final long id) throws NotFoundException
	{
		final Thread thread = this.getThread(id) ;
		for (final Long postId: thread.getPosts())
		{
			this.deletePost(postId) ;
		}
		this.threads.destroy(id) ;
	}

	/**
	 * @param id id of the directory to search.
	 * @return true if there exists a directory whose id is 'id', false otherwise.
	 */
	public boolean directoryExists(final long id)
	{
		return this.directories.contains(id) ;
	}

	/**
	 * flush everything in memory to persistence.
	 * @post all information is in persistence.
	 */
	public void flush()
	{
		this.directories.flush() ;
		this.threads.flush() ;
		this.posts.flush() ;
	}

	/**
	 * flush all information about a directory to persistence.
	 * @param directory directory to flush.
	 * @post the given record is updated in persistence.
	 */
	public void flush(final Directory directory)
	{
		this.directories.flush() ;
	}

	/**
	 * flush all information about a post to persistence.
	 * @param post post to flush.
	 * @post the given record is updated in persistence.
	 */
	public void flush(final Post post)
	{
		this.posts.flush() ;
	}

	/**
	 * flush all information about a thread to persistence.
	 * @param thread thread to flush.
	 * @post the given record is updated in persistence.
	 */
	public void flush(final Thread thread)
	{
		this.threads.flush() ;
	}

	/**
	 * get a whole set of directories. nonexistent directories are ignored.
	 * @param ids set of id's to find directories for.
	 * @return set of directories such that this.contains(directory id). the set may be empty.
	 * @throws NotFoundException if one of the id's was not found.
	 * @pre for (id: ids) this.directoryExists(id)
	 * @post result->getId() = ids
	 */
	public Set<Directory> getAllDirectories(final Set<Long> ids) throws NotFoundException
	{
		Set<Directory> result = new HashSet<Directory>() ;
		for (Long id: ids)
		{
			if (this.directories.contains(id))
			{
				result.add(this.directories.get(id)) ;
			}
		}
		return result ;
	}

	/**
	 * get a whole set of posts. nonexistent posts are ignored.
	 * @param ids set of id's to find posts for.
	 * @return set of posts such that this.contains(post id). the set may be empty.
	 * @throws NotFoundException if one of the id's was not found.
	 * @pre for (id: ids) this.postExists(id)
	 * @post result->getId() = ids
	 */
	public Set<Post> getAllPosts(final Set<Long> ids) throws NotFoundException
	{
		Set<Post> result = new HashSet<Post>() ;
		for (Long id: ids)
		{
			if (this.posts.contains(id))
			{
				result.add(this.posts.get(id)) ;
			}
		}
		return result ;
	}

	/**
	 * get a whole set of threads. nonexistent threads are ignored.
	 * @param ids set of id's to find threads for.
	 * @return set of threads such that this.contains(thread id). the set may be empty.
	 * @throws NotFoundException if one of the id's was not found.
	 * @pre for (id: ids) this.threadExists(id)
	 * @post result->getId() = ids
	 */
	public Set<Thread> getAllThreads(final Set<Long> ids) throws NotFoundException
	{
		Set<Thread> result = new HashSet<Thread>() ;
		for (Long id: ids)
		{
			if (this.threads.contains(id))
			{
				result.add(this.threads.get(id)) ;
			}
		}
		return result ;
	}

	/**
	 * @param id id of the directory to get.
	 * @return directory whose id is 'id'.
	 * @throws NotFoundException if no such directory exists.
	 * @pre this.directoryExists(id)
	 */
	public Directory getDirectory(final long id) throws NotFoundException
	{
		return this.directories.get(id) ;
	}

	/**
	 * @param id id of the post to get.
	 * @return post whose id is 'id'.
	 * @throws NotFoundException if no such post exists.
	 * @pre this.postExists(id)
	 */
	public Post getPost(final long id) throws NotFoundException
	{
		return this.posts.get(id) ;
	}

	/**
	 * @param id id of the thread to get.
	 * @return thread whose id is 'id'.
	 * @throws NotFoundException if no such thread exists.
	 * @pre this.threadExists(id)
	 */
	public Thread getThread(final long id) throws NotFoundException
	{
		return this.threads.get(id) ;
	}

	/**
	 * @param id id of the post to search.
	 * @return true if there exists a post whose id is 'id', false otherwise.
	 */
	public boolean postExists(final long id)
	{
		return this.posts.contains(id) ;
	}

	/**
	 * @param id id of the thread to search.
	 * @return true if there exists a thread whose id is 'id', false otherwise.
	 */
	public boolean threadExists(final long id)
	{
		return this.threads.contains(id) ;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		return "directories on " + this.directories + "; threads on " + this.threads + "; posts on "
			+ this.posts ;
	}
}
